/****************************************************************************
 *
 * Copyright 2016 Samsung Electronics All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 ****************************************************************************/
/****************************************************************************
 * fs/procfs/fs_procfscm.c
 *
 *   Copyright (C) 2014 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/
/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <tinyara/config.h>

#include <sys/types.h>
#include <sys/statfs.h>
#include <sys/stat.h>

#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <tinyara/arch.h>
#include <tinyara/sched.h>
#include <tinyara/kmalloc.h>
#include <tinyara/fs/fs.h>
#include <tinyara/fs/procfs.h>
#include <tinyara/fs/dirent.h>
#include <tinyara/clock.h>

#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>

#if defined(CONFIG_ARCH_BOARD_SIDK_S5JT200)
#include <tinyara/wifi/slsi/slsi_wifi_api.h>
#endif

#if !defined(CONFIG_DISABLE_MOUNTPOINT) && defined(CONFIG_FS_PROCFS)
#if defined(CONFIG_CM) && !defined(CONFIG_FS_PROCFS_EXCLUDE_CONNECTIVITY)

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/
/* Determines the size of an intermediate buffer that must be large enough
 * to handle the longest line generated by this logic.
 */
#define CM_IPADDR_LEN           32
#define CM_LINELEN              32
#define CM_NWBEARER_WLAN        21
#define CM_NWBEARER_BLUETOOTH   22
#define CM_NWBEARER_WPAN        23
#define CM_NWBEARER_ETHERNET    41

/****************************************************************************
 * Private Types
 ****************************************************************************/
/* This enumeration identifies all of the task/thread nodes that can be
 * accessed via the procfs file system.
 */

enum cm_node_e {
	CM_LEVEL0 = 0,				/* The top-level directory */
	CM_NWBEARER,				/* Network Bearer */
	CM_AVAILABLE_BEARER,		/* Available bearers */
	CM_RSSI,					/* Received Signal Strength Indicator */
	CM_IPADDR,					/* Device IP */
	CM_BITRATE					/* Device bitrate */
};

struct cm_node_s {
	FAR const char *relpath;	/* Relative path to the node */
	FAR const char *name;		/* Terminal node segment name */
	uint8_t nodetype;			/* Type of node (see enum proc_node_e) */
	uint8_t dtype;				/* dirent type (see include/dirent.h) */
};

struct cm_dir_s {
	struct procfs_dir_priv_s base;	/* Base directory private data */
	FAR const struct cm_node_s *node;	/* Directory node description */
};

/* This structure describes one open "file" */
struct cm_file_s {
	struct procfs_file_s base;	/* Base open file structure */
	FAR const struct cm_node_s *node;	/* Describes the file node */
	struct cm_dir_s dir;		/* Reference to item being accessed */
	char line[CM_LINELEN];		/* Pre-allocated buffer for formatted lines */
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/* File system methods */
static int cm_open(FAR struct file *filep, FAR const char *relpath, int oflags, mode_t mode);
static int cm_close(FAR struct file *filep);
static ssize_t cm_read(FAR struct file *filep, FAR char *buffer, size_t buflen);

static int cm_dup(FAR const struct file *oldp, FAR struct file *newp);

static int cm_opendir(const char *relpath, FAR struct fs_dirent_s *dir);
static int cm_closedir(FAR struct fs_dirent_s *dir);
static int cm_readdir(FAR struct fs_dirent_s *dir);
static int cm_rewinddir(FAR struct fs_dirent_s *dir);

static int cm_stat(FAR char *relpath, FAR struct stat *buf);

/****************************************************************************
 * Private Variables
 ****************************************************************************/

/****************************************************************************
 * Public Variables
 ****************************************************************************/

/* See fs_mount.c -- this structure is explicitly externed there.
 * We use the old-fashioned kind of initializers so that this will compile
 * with any compiler.
 */

const struct procfs_operations cm_operations = {
	cm_open,					/* open */
	cm_close,					/* close */
	cm_read,					/* read */
	NULL,						/* write */

	cm_dup,						/* dup */

	cm_opendir,					/* opendir */
	cm_closedir,				/* closedir */
	cm_readdir,					/* readdir */
	cm_rewinddir,				/* rewinddir */

	cm_stat						/* stat */
};

/* These structures provide information about every node */

static const struct cm_node_s g_cm_level0 = {
	"", "connectivity", (uint8_t)CM_LEVEL0, DTYPE_DIRECTORY	/* Top-level directory */
};

static const struct cm_node_s g_cm_nwbearer = {
	"nwbearer", "nwbearer", (uint8_t)CM_NWBEARER, DTYPE_FILE	/* Task command line */
};

static const struct cm_node_s g_cm_available_bearer = {
	"available_bearer", "available_bearer", (uint8_t)CM_AVAILABLE_BEARER, DTYPE_FILE	/* Task command line */
};

static const struct cm_node_s g_cm_rssi = {
	"rssi", "rssi", (uint8_t)CM_RSSI, DTYPE_FILE	/* Task command line */
};

static const struct cm_node_s g_cm_ipaddr = {
	"ipaddr", "ipaddr", (uint8_t)CM_IPADDR, DTYPE_FILE	/* Task command line */
};

static const struct cm_node_s g_cm_bitrate = {
	"bitrate", "bitrate", (uint8_t)CM_BITRATE, DTYPE_FILE	/* Task command line */
};

/* This is the list of all nodes */

static FAR const struct cm_node_s *const g_cm_nodeinfo[] = {
	&g_cm_level0,
	&g_cm_nwbearer,				/* network bearer used by LWM2M */
	&g_cm_available_bearer,		/* List of available bearers */
	&g_cm_rssi,					/* Rssi */
	&g_cm_ipaddr,				/* IP address */
	&g_cm_bitrate,				/* bitrate */
};

#define CM_NNODES (sizeof(g_cm_nodeinfo)/sizeof(FAR const struct cm_node_s * const))

static const struct cm_node_s *const g_cm_level0info[] = {
	&g_cm_nwbearer,				/* Task command line */
	&g_cm_available_bearer,		/* Task command line */
	&g_cm_rssi,					/* Task command line */
	&g_cm_ipaddr,				/* Task command line */
	&g_cm_bitrate,				/* Task command line */
};

#define CM_NLEVEL0NODES (sizeof(g_cm_level0info)/sizeof(FAR const struct cm_node_s * const))

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: cm_get_interface
 ****************************************************************************/
static void cm_get_interface(char *mac)
{
#if defined(CONFIG_NET_ETHERNET)
	strcpy(mac, "eth0");
#elif defined(CONFIG_NET_802154)
	strcpy(mac, "wlan0");
#endif
}

/****************************************************************************
 * Name: cm_nwbearer_read
 ****************************************************************************/
static size_t cm_nwbearer_read(FAR struct cm_file_s *connectivityfile, FAR char *buffer, size_t buflen, off_t offset)
{
	size_t linesize;
	size_t copysize;
	int nwbearer;
#if defined(CONFIG_NET_ETHERNET)
	nwbearer = CM_NWBEARER_ETHERNET;
#elif defined(CONFIG_NET_802154)
	nwbearer = CM_NWBEARER_WPAN;
#endif
	linesize = snprintf(connectivityfile->line, CM_LINELEN, "%d", nwbearer);
	copysize = procfs_memcpy(connectivityfile->line, linesize, buffer, buflen, &offset);
	return copysize;
}

/****************************************************************************
 * Name: cm_available_bearer_read
 ****************************************************************************/
static size_t cm_available_bearer_read(FAR struct cm_file_s *connectivityfile, FAR char *buffer, size_t buflen, off_t offset)
{
	size_t linesize;
	size_t copysize;
	int nwbearer;
#if defined(CONFIG_NET_ETHERNET)
	nwbearer = CM_NWBEARER_ETHERNET;
#elif defined(CONFIG_NET_802154)
	nwbearer = CM_NWBEARER_WPAN;
#endif
	linesize = snprintf(connectivityfile->line, CM_LINELEN, "%d", nwbearer);
	copysize = procfs_memcpy(connectivityfile->line, linesize, buffer, buflen, &offset);
	return copysize;
}

/****************************************************************************
 * Name: cm_rssi_read
 ****************************************************************************/
static size_t cm_rssi_read(FAR struct cm_file_s *connectivityfile, FAR char *buffer, size_t buflen, off_t offset)
{
	size_t linesize;
	size_t copysize;
	int rssi_value;
#if defined(CONFIG_ARCH_BOARD_SIDK_S5JT200)
	WiFiGetRssi(&rssi_value);
	rssi_value -= 255;
#else
	rssi_value = 0;
#endif

	linesize = snprintf(connectivityfile->line, CM_LINELEN, "%d", rssi_value);
	copysize = procfs_memcpy(connectivityfile->line, linesize, buffer, buflen, &offset);

	return copysize;
}

/****************************************************************************
 * Name: cm_ipaddr_read
 ****************************************************************************/
static size_t cm_ipaddr_read(FAR struct cm_file_s *connectivityfile, FAR char *buffer, size_t buflen, off_t offset)
{
	char mac[4];
	char ipaddr[CM_IPADDR_LEN];
	size_t linesize;
	size_t copysize;

	cm_get_interface(mac);
#ifdef CONFIG_NET_IPv4
	struct in_addr addr;
	netlib_get_ipv4addr(mac, &addr);
	inet_ntop(AF_INET, &addr, ipaddr, INET_ADDRSTRLEN);
#endif

	linesize = snprintf(connectivityfile->line, CM_LINELEN, "%s", ipaddr);
	copysize = procfs_memcpy(connectivityfile->line, linesize, buffer, buflen, &offset);

	return copysize;
}

/****************************************************************************
 * Name: cm_bitrate_read
 ****************************************************************************/
static size_t cm_bitrate_read(FAR struct cm_file_s *connectivityfile, FAR char *buffer, size_t buflen, off_t offset)
{
	size_t linesize;
	size_t copysize;
	int bitrate;
	bitrate = 0;

	linesize = snprintf(connectivityfile->line, CM_LINELEN, "%d", bitrate);
	copysize = procfs_memcpy(connectivityfile->line, linesize, buffer, buflen, &offset);

	return copysize;
}

/****************************************************************************
 * Name: cm_findnode
 ****************************************************************************/
static FAR const struct cm_node_s *cm_findnode(FAR const char *relpath)
{
	int i;

	/* Two path forms are accepted:
	 *
	 * "connectivity" - It is a top directory.
	 * "connectivity/<node>" - If <node> is a recognized node then, then it
	 *   is a file or directory.
	 */

	if (strncmp(relpath, "connectivity", 12) != 0) {
		fdbg("ERROR: Bad relpath: %s\n", relpath);
		return NULL;
	}
	relpath += 12;

	if (relpath[0] == '/') {
		relpath++;
	}

	/* Search every string in g_cm_nodeinfo or until a match is found */

	for (i = 0; i < CM_NNODES; i++) {
		if (strcmp(g_cm_nodeinfo[i]->relpath, relpath) == 0) {
			return g_cm_nodeinfo[i];
		}
	}

	/* Not found */

	return NULL;
}

/****************************************************************************
 * Name: cm_open
 ****************************************************************************/

static int cm_open(FAR struct file *filep, FAR const char *relpath, int oflags, mode_t mode)
{
	FAR struct cm_file_s *connectivityfile;
	FAR const struct cm_node_s *node;

	fvdbg("Open '%s'\n", relpath);

	/* PROCFS is read-only.  Any attempt to open with any kind of write
	 * access is not permitted.
	 *
	 * REVISIT:  Write-able proc files could be quite useful.
	 */

	if ((oflags & O_WRONLY) != 0 || (oflags & O_RDONLY) == 0) {
		fdbg("ERROR: Only O_RDONLY supported\n");
		return -EACCES;
	}

	/* Find the nodes of connectivity directory */
	node = cm_findnode(relpath);
	if (!node) {
		/* Entry not found */
		fdbg("ERROR: Invalid path \"%s\"\n", relpath);
		return -ENOENT;
	}

	if (!DIRENT_ISFILE(node->dtype)) {
		fdbg("ERROR: Path \"%s\" is not a regular file\n", relpath);
		return -EISDIR;
	}

	/* Allocate a container to hold the domain selection */
	connectivityfile = (FAR struct cm_file_s *)kmm_zalloc(sizeof(struct cm_file_s));
	if (!connectivityfile) {
		fdbg("ERROR: Failed to allocate file container\n");
		return -ENOMEM;
	}

	connectivityfile->node = node;

	/* Save the index as the open-specific state in filep->f_priv */
	filep->f_priv = (FAR void *)connectivityfile;

	return OK;
}

/****************************************************************************
 * Name: cm_close
 ****************************************************************************/

static int cm_close(FAR struct file *filep)
{
	FAR struct cm_file_s *connectivityfile;

	/* Recover our private data from the struct file instance */

	connectivityfile = (FAR struct cm_file_s *)filep->f_priv;
	DEBUGASSERT(connectivityfile);

	/* Release the file container structure */

	kmm_free(connectivityfile);
	filep->f_priv = NULL;
	return OK;
}

/****************************************************************************
 * Name: cm_read
 ****************************************************************************/

static ssize_t cm_read(FAR struct file *filep, FAR char *buffer, size_t buflen)
{
	FAR struct cm_file_s *connectivityfile;
	ssize_t ret;

	fvdbg("buffer=%p buflen=%d\n", buffer, (int)buflen);

	/* Recover our private data from the struct file instance */

	connectivityfile = (FAR struct cm_file_s *)filep->f_priv;
	DEBUGASSERT(connectivityfile);

	/* Provide the requested data */
	ret = 0;

	switch (connectivityfile->node->nodetype) {
	case CM_NWBEARER:			/* Network bearer */
		ret = cm_nwbearer_read(connectivityfile, buffer, buflen, filep->f_pos);
		break;
	case CM_AVAILABLE_BEARER:	/* List of available bearers */
		ret = cm_available_bearer_read(connectivityfile, buffer, buflen, filep->f_pos);
		break;
	case CM_RSSI:				/* RSSI */
		ret = cm_rssi_read(connectivityfile, buffer, buflen, filep->f_pos);
		break;
	case CM_IPADDR:			/* Device IP address */
		ret = cm_ipaddr_read(connectivityfile, buffer, buflen, filep->f_pos);
		break;
	case CM_BITRATE:			/* Device bitrate */
		ret = cm_bitrate_read(connectivityfile, buffer, buflen, filep->f_pos);
		break;
	default:
		ret = -EINVAL;
		break;
	}

	/* Update the file offset */

	if (ret > 0) {
		filep->f_pos += ret;
	}

	return ret;
}

/****************************************************************************
 * Name: cm_dup
 *
 * Description:
 *   Duplicate open file data in the new file structure.
 *
 ****************************************************************************/
static int cm_dup(FAR const struct file *oldp, FAR struct file *newp)
{
	FAR struct cm_file_s *oldfile;
	FAR struct cm_file_s *newfile;

	fvdbg("Dup %p->%p\n", oldp, newp);

	/* Recover our private data from the old struct file instance */

	oldfile = (FAR struct cm_file_s *)oldp->f_priv;
	DEBUGASSERT(oldfile);

	/* Allocate a new container to hold the task and node selection */

	newfile = (FAR struct cm_file_s *)kmm_malloc(sizeof(struct cm_file_s));
	if (!newfile) {
		fdbg("ERROR: Failed to allocate file container\n");
		return -ENOMEM;
	}

	/* The copy the file information from the old container to the new */

	memcpy(newfile, oldfile, sizeof(struct cm_file_s));

	/* Save the new container in the new file structure */

	newp->f_priv = (FAR void *)newfile;
	return OK;
}

/****************************************************************************
 * Name: cm_opendir
 *
 * Description:
 *   Open a directory for read access
 *
 ****************************************************************************/
static int cm_opendir(FAR const char *relpath, FAR struct fs_dirent_s *dir)
{
	FAR struct cm_dir_s *connectivitydir;
	FAR const struct cm_node_s *node;

	fvdbg("relpath: \"%s\"\n", relpath ? relpath : "NULL");
	DEBUGASSERT(relpath && dir && !dir->u.procfs);

	/* Find the directory entry being opened */
	node = cm_findnode(relpath);
	if (!node) {
		/* Entry not found */
		fdbg("ERROR: Invalid path \"%s\"\n", relpath);
		return -ENOENT;
	}

	if (!DIRENT_ISDIRECTORY(node->dtype)) {
		fdbg("ERROR: Path \"%s\" is not a regular directory\n", relpath);
		return -ENOTDIR;
	}

	connectivitydir = (FAR struct cm_dir_s *)kmm_zalloc(sizeof(struct cm_dir_s));
	if (!connectivitydir) {
		fdbg("ERROR: Failed to allocate the directory structure\n");
		return -ENOMEM;
	}

	if (node->nodetype == CM_LEVEL0) {
		/* This is a top level directory : connectivity */
		connectivitydir->base.level = 1;
		connectivitydir->base.nentries = CM_NLEVEL0NODES;
		connectivitydir->base.index = 0;
		connectivitydir->node = node;
	}

	dir->u.procfs = (FAR void *)connectivitydir;
	return OK;
}

/****************************************************************************
 * Name: cm_closedir
 *
 * Description: Close the directory listing
 *
 ****************************************************************************/

static int cm_closedir(FAR struct fs_dirent_s *dir)
{
	FAR struct cm_dir_s *priv;

	DEBUGASSERT(dir && dir->u.procfs);
	priv = dir->u.procfs;

	if (priv) {
		kmm_free(priv);
	}

	dir->u.procfs = NULL;
	return OK;
}

/****************************************************************************
 * Name: cm_readdir
 *
 * Description: Read the next directory entry
 *
 ****************************************************************************/

static int cm_readdir(struct fs_dirent_s *dir)
{
	FAR struct cm_dir_s *connectivitydir;
	FAR const struct cm_node_s *node = NULL;
	unsigned int index;
	int ret;

	DEBUGASSERT(dir && dir->u.procfs);
	connectivitydir = dir->u.procfs;

	/* Have we reached the end of the directory */

	index = connectivitydir->base.index;
	if (index >= connectivitydir->base.nentries) {
		/* We signal the end of the directory by returning the special
		 * error -ENOENT
		 */

		fdbg("Entry %d: End of directory\n", index);
		ret = -ENOENT;
	}
	/* No, we are not at the end of the directory */
	else {
		/* Handle the directory listing by the node type */

		switch (connectivitydir->node->nodetype) {
		case CM_LEVEL0:		/* Top level directory */
			DEBUGASSERT(connectivitydir->base.level == 1);
			node = g_cm_level0info[index];
			break;
		default:
			return -ENOENT;
		}

		/* Save the filename and file type */

		dir->fd_dir.d_type = node->dtype;
		strncpy(dir->fd_dir.d_name, node->name, NAME_MAX + 1);

		/* Set up the next directory entry offset.      NOTE that we could use the
		 * standard f_pos instead of our own private index.
		 */

		connectivitydir->base.index = index + 1;
		ret = OK;
	}

	return ret;
}

/****************************************************************************
 * Name: cm_rewindir
 *
 * Description: Reset directory read to the first entry
 *
 ****************************************************************************/

static int cm_rewinddir(struct fs_dirent_s *dir)
{
	FAR struct cm_dir_s *priv;

	DEBUGASSERT(dir && dir->u.procfs);
	priv = dir->u.procfs;

	priv->base.index = 0;
	return OK;
}

/****************************************************************************
 * Name: cm_stat
 *
 * Description: Return information about a file or directory
 *
 ****************************************************************************/

static int cm_stat(char *relpath, struct stat *buf)
{
	FAR const struct cm_node_s *node;

	/* Find the directory entry being opened */
	node = cm_findnode(relpath);
	if (!node) {
		fdbg("ERROR: Invalid path \"%s\"\n", relpath);
		return -ENOENT;
	}

	/* If the node exists, it is the name for a read-only file or
	 * directory.
	 */

	if (node->dtype == DTYPE_FILE) {
		buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR;
	} else {
		buf->st_mode = S_IFDIR | S_IROTH | S_IRGRP | S_IRUSR;
	}

	/* File/directory size, access block size */

	buf->st_size = 0;
	buf->st_blksize = 0;
	buf->st_blocks = 0;
	return OK;

}
#endif
#endif
